package com.tecklab.demo.config;


import com.tecklab.demo.handler.CustomHttpRetryHandler;
import com.tecklab.demo.utils.HttpUtils;
import lombok.Data;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.client.BackoffManager;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author sunking
 * @date 2022/12/13 上午12:48
 **/
@Configuration
@Data
@ConfigurationProperties(prefix = "demo.httpclient")
public class HttpClientConfig {

    protected Logger log = LoggerFactory.getLogger(getClass());

    private Integer maxTotal = 20;

    private Integer defaultMaxPerRoute = 10;

    private Integer connectionRequestTimeout = 5000;

    private Integer connectTimeout = 5000;

    private Integer socketTimeout = 5000;

    private Integer defaultKeepAliveTimeMillis = 5000;

    private Integer closeIdleConnectionWaitTimeSecs = 300;

    private Integer aysncThreadPollSize = 10;

    private int retryCount = 0;

    /**
     * 是否可以在请求成功发出后重试
     */
    private boolean requestSentRetryEnabled = true;

    // 用于动态调整失败的HttpRoute的ConnectionPool大小
    private double backoffFactor = 0.5;
    // 调整失败的HttpRoute的ConnectionPool大小的时间间隔
    private long coolDown = 5 * 1000L;

    private boolean logEnable = true;

    @Bean
    public PoolingHttpClientConnectionManager poolingConnectionManager() {
        SSLContextBuilder builder = new SSLContextBuilder();
        try {
            builder.loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) {
                    return true;
                }
            });
        } catch (NoSuchAlgorithmException | KeyStoreException e) {
            log.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
        }

        SSLConnectionSocketFactory sslsf = null;
        try {
            sslsf = new SSLConnectionSocketFactory(builder.build());
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            log.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
        }

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                .<ConnectionSocketFactory>create()
                .register("https", sslsf)
                .register("http", new PlainConnectionSocketFactory())
                .build();

        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        poolingConnectionManager.setMaxTotal(maxTotal);  //最大连接数
        poolingConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);  //同路由并发数

        // 空闲连接关闭
        IdleConnectionEvictor idleConnectionEvictor = new IdleConnectionEvictor(poolingConnectionManager, closeIdleConnectionWaitTimeSecs, TimeUnit.SECONDS);
        idleConnectionEvictor.start();

        return poolingConnectionManager;
    }

    @Bean
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, httpContext) -> {
            HeaderElementIterator it = new BasicHeaderElementIterator
                    (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return defaultKeepAliveTimeMillis;
        };
    }

    @Bean
    public BackoffManager backoffManager(){
        AIMDBackoffManager backoffManager = new AIMDBackoffManager(poolingConnectionManager());
        backoffManager.setCooldownMillis(coolDown);
        backoffManager.setBackoffFactor(backoffFactor);

        return backoffManager;
    }


    @Bean
    public HttpRequestRetryHandler httpRequestRetryHandler(){
        List<Class<? extends IOException>> nonRetriableClasses = Arrays.asList(
//                InterruptedIOException .class,
                UnknownHostException .class,
                ConnectException .class,
                SSLException .class);
        return new CustomHttpRetryHandler(retryCount, requestSentRetryEnabled, nonRetriableClasses);
    }

    @Bean
    public CloseableHttpClient httpClient() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();

        return HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(poolingConnectionManager())
                .setKeepAliveStrategy(connectionKeepAliveStrategy())
                .setRetryHandler(httpRequestRetryHandler())
                .setConnectionBackoffStrategy(new DefaultBackoffStrategy())
                .setBackoffManager(backoffManager())
                .build();
    }

    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory();
        rf.setHttpClient(httpClient());
        return rf;
    }

    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory4Async() {
        HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory();
        rf.setHttpClient(httpClient());
        return rf;
    }

    @Bean
    public RestTemplate restTemplate() {
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
        messageConverters.add(new FormHttpMessageConverter());
        messageConverters.add(new MappingJackson2HttpMessageConverter());

        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
        restTemplate.setMessageConverters(messageConverters);

        // 设置HttpUtils工具类
        HttpUtils.setRestTemplate(restTemplate);
        HttpUtils.setLogEnable(logEnable);

        return restTemplate;
    }

    @Bean
    public AsyncListenableTaskExecutor asyncListenableTaskExecutor() {
        ThreadPoolTaskScheduler taskExecutor = new ThreadPoolTaskScheduler();
        taskExecutor.setThreadNamePrefix("async-httpclient-exec-");
        taskExecutor.setPoolSize(aysncThreadPollSize);
        return taskExecutor;
    }

    @Bean
    public AsyncRestTemplate asyncRestTemplate() {
        SimpleClientHttpRequestFactory asyncClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        asyncClientHttpRequestFactory.setConnectTimeout(connectTimeout);
        asyncClientHttpRequestFactory.setReadTimeout(socketTimeout);
        asyncClientHttpRequestFactory.setTaskExecutor(asyncListenableTaskExecutor());

        AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(asyncClientHttpRequestFactory, clientHttpRequestFactory4Async());

//        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
//        messageConverters.add(new ByteArrayHttpMessageConverter());
//        messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
//        messageConverters.add(new FormHttpMessageConverter());
//        messageConverters.add(jackson2HttpMessageConverter);
//        asyncRestTemplate.setMessageConverters(messageConverters);

        // 设置HttpUtils工具类
        HttpUtils.setAsyncRestTemplate(asyncRestTemplate);

        return asyncRestTemplate;
    }

}
